package com.iteaj.network;

import com.iteaj.network.message.VoidMessageBody;
import com.iteaj.network.utils.ByteUtil;
import io.netty.buffer.ByteBuf;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.NonNull;

import java.io.IOException;
import java.net.ProtocolException;

/**
 * <p>此报文未进行报文体解析, 将在如下两个地方解析对应的报文体(不进行严格限制, 对于简单的设备也可以在报文{@link #build()}直接解析)</p>
 * @see AbstractProtocol#buildRequestMessage() 在协议里面解析对应的报文体
 * @see AbstractProtocol#buildResponseMessage() 在协议里面解析对应的报文体
 * 要求一种设备类型需要对应一种报文 如下：
 * {@code ServerComponentFactory#getByClass(AbstractMessage)} 通过不同的报文获取相应的组件
 * Create Date By 2017-09-11
 * @author iteaj
 * @since 1.7
 */
public abstract class AbstractMessage implements Message {

    /**报文数据*/
    protected byte[] message;
    @NonNull
    protected MessageBody messageBody;
    @NonNull
    protected MessageHead messageHead;

    protected static final byte[] emptyMessage = new byte[]{};

    /** 日志 */
    protected Logger logger = LoggerFactory.getLogger(getClass());
    /**空报文体*/
    public static final VoidMessageBody VOID_MESSAGE_BODY = VoidMessageBody.getInstance();

    /**
     * 通过报文内容创建一个报文对象(一般用于接受客户端的请求或者响应)
     * @see #doBuild(byte[])
     * @param message
     */
    public AbstractMessage(byte[] message) {
        if(null == message)
            throw new IllegalArgumentException("参数不能为空");
        this.message = message;
    }

    /**
     * 通过报文头和报文体构建报文(一般用于平台主动请求或者响应客户端)
     * @see #VOID_MESSAGE_BODY 默认是用空报文体
     * @see #doBuild(MessageHead, MessageBody)
     * @param messageHead
     */
    public AbstractMessage(@NonNull MessageHead messageHead) {
        this(messageHead, VOID_MESSAGE_BODY);
    }

    /**
     * 通过报文头和报文体构建报文(一般用于平台主动请求或者响应客户端)
     * @see #doBuild(MessageHead, MessageBody)
     * @param messageHead
     * @param messageBody
     */
    public AbstractMessage(@NonNull MessageHead messageHead, @NonNull MessageBody messageBody) {
        this.messageBody = messageBody;
        this.messageHead = messageHead;
    }

    @Deprecated
    public AbstractMessage(ByteBuf byteBuf) {
        try {
            if(null == byteBuf)
                throw new IllegalArgumentException("参数不能为空");

            byteBuf.retain();
            this.message = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(message);
        } finally {
            // 释放内存缓冲
            ReferenceCountUtil.release(byteBuf);
        }
    }
    /**
     * 用于将{@link #message} 解析成报文头和报文体
     * @see #messageHead 解析的报文头必须不为空, 需要包含 type、messageId等
     * @see #messageBody 可空
     * @return 解析后的报文 or this
     * @throws IOException
     */
    public AbstractMessage build() throws IOException {
        if(this.message != null) {
            doBuild(this.message);
            return this;
        }

        if(this.getHead() != null && this.getBody() != null) {
            doBuild(this.getHead(), this.getBody());
            return this;
        }

        throw new ProtocolException("错误的报文[message == null]");
    }

    /**
     * 将报文头和报文体的内容合并成{@link #message}报文
     * @param head
     * @param body
     */
    protected void doBuild(MessageHead head, MessageBody body) {
        // 创建报文对象
        int length = head.getLength() + body.getLength();
        this.message = new byte[length];
        ByteUtil.addBytes(this.message, head.getMessage(), 0);
        ByteUtil.addBytes(this.message, body.getMessage(), head.getLength());
    }

    /**
     * @see #message 解析成{@link #messageHead} 和 {@link #messageBody}
     * @param message
     */
    protected abstract void doBuild(byte[] message);

    @Override
    public int length() {
        return message.length;
    }

    @Override
    public byte[] getMessage() {
        return message;
    }


    @Override
    public MessageHead getHead() {
        return messageHead;
    }

    /**
     * 返回 {@link VoidMessageBody}
     * @return
     */
    @Override
    public MessageBody getBody() {
        return messageBody;
    }

    public void setBody(MessageBody messageBody) {
        this.messageBody = messageBody;
    }

    public void setHead(MessageHead messageHead) {
        this.messageHead = messageHead;
    }

    public void setMessage(byte[] message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return message != null ? ByteUtil.bytesToHex(message) : "null";
    }
}
